package com.cellulam.log;

import com.cellulam.framework.core.model.DataResult;
import com.cellulam.framework.core.utils.JacksonUtils;
import com.cellulam.framework.core.utils.TextUtils;
import com.cellulam.framework.core.exceptions.BusinessException;
import com.cellulam.framework.core.exceptions.SystemErrorStatus;
import com.cellulam.framework.core.auth.SimpleUserClient;
import com.cellulam.framework.core.auth.UserClient;
import com.cellulam.framework.core.log.*;
import com.cellulam.framework.core.log.enums.LogLevel;
import com.cellulam.framework.core.log.enums.SystemLogType;
import com.cellulam.log.interaction.LogServiceAgent;
import com.cellulam.log.interaction.dto.AccessLogRequest;
import com.cellulam.log.interaction.dto.BizLogRequest;
import com.cellulam.log.interaction.dto.SystemLogRequest;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;


@Slf4j
public class DurableLogClient implements LogClient {

    private static final ExecutorService THREAD_POOL = Executors.newFixedThreadPool(5);

    private final LogServiceAgent logServiceAgent;

    private final String applicationName;

    private UserClient userClient;

    private int httpTimeout = 3000;

    public void setUserClient(UserClient userClient) {
        this.userClient = userClient;
    }

    public void setHttpTimeout(int httpTimeout) {
        this.httpTimeout = httpTimeout;
    }

    public DurableLogClient(String logServiceHost, String currentApplicationName) {
        this.userClient = SimpleUserClient.getInstance();
        this.logServiceAgent = new LogServiceAgent(logServiceHost);
        this.applicationName = currentApplicationName;
    }

    @Override
    public void writeBizLog(BizLog log) {
        this.writeBizLog(this.getUserId(), log);
    }

    @Override
    public void writeBizLog(long userId, BizLog log) {
        execute(() -> {
            BizLogRequest request = new BizLogRequest();
            request.setType(log.getType().getCode());
            request.setAction(log.getAction());
            request.setIdentifier(log.getIdentifier());
            request.setObjValue1(log.getObjValue1());
            request.setObjValue2(log.getObjValue2());
            request.setObjValue3(log.getObjValue3());
            request.setObjValue4(log.getObjValue4());
            request.setCmdString(log.getCmdString());
            request.setSource(applicationName);
            request.setUserId(userId);
            request.setMemo(log.getMemo());

            this.logServiceAgent.writeLog(request);
        });
    }

    @Override
    public void writeRestLog(RestLog log, Exception ex) {
        long userId = this.getUserId();
        execute(() -> {

            SystemLogRequest request = new SystemLogRequest();
            request.setType(SystemLogType.REST_REQUEST.getCode());
            request.setIdentifier(userId);
            request.setUrl(log.getUri().getPath());
            request.setStatus(log.getResponseStatus());
            request.setMessage(log.getResponseStatusText());
            request.setCost((int) log.getCost());
            request.setSource(applicationName);
            request.setUserId(userId);

            String memo = TextUtils.compress(String.format("uri: [%s];requestHeaders:[%s];requestBody:[%s];responseHeaders:[%s];responseBody:[%s];",
                    log.getUri(),
                    log.getRequestHeaders(),
                    log.getRequestBody(),
                    log.getResponseHeaders(),
                    log.getResponseBody()));

            request.setMemo(memo.length() > 5000 ? memo.substring(0, 5000) + "..." : memo);

            if (ex != null) {
                request.setLevel(LogLevel.ERROR.getCode());
                request.setExceptName(ex.getClass().getName());
                request.setCauseExceptName(ex.getCause() == null ? null : ex.getCause().getClass().getName());
            } else {
                request.setLevel(LogLevel.DEBUG.getCode());
            }

            this.logServiceAgent.writeLog(request);
        });
    }

    @Override
    public void writeSystemLog(Exception ex) {
        this.writeSystemLog(ex, LogLevel.ERROR, SystemErrorStatus.SYS_ERROR.getStatus());
    }

    @Override
    public void writeSystemLog(Exception ex, LogLevel logLevel, int status) {
        this.writeSystemLog(this.getUserId(), logLevel, ex, status);
    }

    @Override
    public void writeSystemLog(long userId, LogLevel logLevel, Exception ex, int status) {
        execute(() -> {
            Class exClass = ex.getClass();

            SystemLogRequest request = new SystemLogRequest();
            request.setIdentifier(userId);
            request.setCauseExceptName(ex.getCause() == null ? null : ex.getCause().getClass().getName());
            request.setExceptName(exClass.getSimpleName());
            request.setUrl(exClass.getName());
            request.setMessage(ex.getMessage());
            request.setSource(applicationName);
            request.setUserId(userId);
            request.setLevel(logLevel.getCode());
            request.setStatus(status);

            if (ex instanceof BusinessException) {
                BusinessException businessException = (BusinessException) ex;
                request.setType(SystemLogType.BUSINESS_FAIL.getCode());
                request.setStatus(businessException.getCode());

            } else {
                request.setType(SystemLogType.SYSTEM_ERROR.getCode());
            }

            this.logServiceAgent.writeLog(request);
        });
    }

    @Override
    public void writeSystemLog(SystemLog log) {
        this.writeSystemLog(this.getUserId(), log);
    }

    @Override
    public void writeSystemLog(long userId, SystemLog info) {

        execute(() -> {
            SystemLogRequest request = new SystemLogRequest();
            request.setIdentifier(info.getIdentifier());
            request.setType(info.getType().getCode());
            request.setExceptName(info.getExceptName());
            request.setLevel(info.getLevel().getCode());
            request.setUrl(info.getUrl());
            request.setStatus(info.getStatus());
            request.setMessage(info.getMessage());
            request.setCost(info.getCost());
            request.setSource(applicationName);
            request.setUserId(userId);
            request.setMemo(info.getMemo());

            this.logServiceAgent.writeLog(request);
        });
    }

    @Override
    public void writeAccessLog(long userId, RequestHttpLog info) {
        execute(() -> {
            AccessLogRequest request = new AccessLogRequest();
            DataResult result = castToResult(info.getResponse());
            request.setMethod(info.getMethod());
            request.setUrl(info.getUrl());
            request.setHttpStatus(info.getHttpStatus());
            request.setStatus(result == null ? null : result.getStatus());
            request.setMessage(result == null ? null : result.getMessage());
            request.setCost((int) info.getCost());
            request.setSource(applicationName);
            request.setIp(info.getIp());
            request.setRequest(TextUtils.compress(String.format("headers:[%s];queryString: [%s];request:[%s]",
                    info.getHeaders(),
                    info.getQueryString(),
                    info.getRequest())));
            request.setResponse(TextUtils.compress(info.getResponse()));
            request.setUserId(userId);

            long id = this.logServiceAgent.writeLog(request);

            if (info.getCost() > httpTimeout) {
                SystemLog systemLog = new SystemLog();
                systemLog.setIdentifier(id);
                systemLog.setType(SystemLogType.SLOW_REQUEST);
                systemLog.setExceptName(SystemLogType.SLOW_REQUEST.toString());
                systemLog.setLevel(LogLevel.WARN);
                systemLog.setUrl(info.getUrl());
                systemLog.setStatus(result == null ? info.getHttpStatus() : result.getStatus());
                systemLog.setMessage(result == null ? info.getMessage() : result.getMessage());
                systemLog.setCost((int) info.getCost());

                writeSystemLog(userId, systemLog);
            }
        });
    }

    @Override
    public void logSlowSql(SlowSqlLog log) {
        execute(() -> {
            SystemLogRequest request = new SystemLogRequest();
            request.setIdentifier(Long.valueOf(SystemLogType.SLOW_SQL.getCode()));
            request.setType(SystemLogType.SLOW_SQL.getCode());
            request.setExceptName(SystemLogType.SLOW_SQL.toString());
            request.setLevel(LogLevel.WARN.getCode());
            request.setUrl(log.getSqlId());
            request.setStatus(SystemErrorStatus.SQL_SLOW.getStatus());
            request.setMessage(SystemErrorStatus.SQL_SLOW.getMessage());
            request.setCost((int) log.getCost());
            request.setSource(applicationName);
            request.setUserId(0L);
            request.setMemo(JacksonUtils.toJson(log));

            this.logServiceAgent.writeLog(request);
        });
    }

    private long getUserId() {
        return userClient == null ? UserClient.DEFAULT_USER_ID : userClient.getUserId();
    }

    private static DataResult castToResult(String response) {
        if (StringUtils.isEmpty(response)) {
            return null;
        }
        try {
            return JacksonUtils.toObj(response, DataResult.class);
        } catch (Exception e) {
            //忽略异常
            return null;
        }
    }

    private static void execute(Runnable runnable) {
        execute(runnable, true);
    }

    private static void execute(Runnable runnable, boolean async) {
        Runnable run = () -> {
            try {
                runnable.run();
            } catch (Exception e) {
                log.error("", e);
            }
        };
        if (async) {
            THREAD_POOL.execute(() -> run.run());
        } else {
            run.run();
        }
    }
}
